package com.yang.framework;

import com.fasterxml.jackson.databind.JsonNode;
import com.yang.annotation.ApiParam;
import com.yang.utils.BeanUtil;
import com.yang.utils.JsonUtil;
import com.yang.utils.SpringUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @Description: 我们解析具体路径映射到的方法信息
 *                  这个类就是我们的目标方法解析处理出来的信息, 比如参数, 返回值, 执行方法等等
 *                  在我们这里就是把ActivityController类的apiTest方法解析成这个类
 * @author: Yang Qiang
 * @create: 2021-11-11 22:42
 */
@Slf4j
@Data
public class ApiMethodInfo {

    // spring容器中管理的bean名称(方法对应的bean)
    private String beanName;

    // 方法的描述
    private String desc;

    // 方法类
    private Method method;

    // 方法返回的类型
    private Class returnType;

    // 这个spring的类可以获取到这个方法上面的所有参数
    static final LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();

    // 方法的参数(参数名字:参数类型)
    private Map<String, ApiParamInfo> paramsMap;


    /**
     * 执行方法
     * @param param 方法形参, 是一个json字符串, 我们需要转换
     * @return
     */
    public Object execute(String param){
        // 从容器中拿到这个类
        Object bean = SpringUtils.getBean(beanName);
        // 执行方法(方法的对象, 和方法的参数(参数需要转换一下))
        try {
            return method.invoke(bean, paramParams(param));
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 转换方法执行时需要的参数
     * 我们知道参数的名称和对应的参数值
     * 也知道参数的json字符串, 所以我们可以解析得到对应的参数
     * @param param
     * @return
     */
    private Object[] paramParams(String param) {
        // 1 创建参数
        List params = new ArrayList();
        // 循环方法对应的所有参数
        for (Map.Entry<String, ApiParamInfo> entry : paramsMap.entrySet()) {
            // 根据参数的名字获取json字符串中相应的参数值
            JsonNode paramValue = JsonUtil.getValue(param, entry.getKey());
            // 参数类型
            ApiParamInfo paramType = entry.getValue();
            Class type = paramType.getParamType();
            // 进行类型的转换(如果是基本数据类型)
            if (BeanUtil.isBaseDataType(type)){
                params.add(JsonUtil.transfor(paramValue, type));
            }else {
                params.add(JsonUtil.toBeanIgnoreUnkown(param, type));
            }

        }
        return params.toArray();
    }


    /**
     * 构建方法的参数列表, 也就是属性: paramsMap  + 返回值
     */
    public void buildParameters(){
        int i= 0;
        paramsMap = new LinkedHashMap<>();
        // 获取方法的所有参数类型
        Class<?>[] parameterTypes = method.getParameterTypes();
        // 获取方法的所有参数名称
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
        // 获取方法上参数的所有注解
        Annotation[][] annotations = method.getParameterAnnotations();
        for (Annotation[] annotation : annotations) {
            // 找到我们自己的注解, 也就是我们自己定义的注解, 然后获取注解上面的参数
            ApiParam apiParam = getApiParamAnnotation(annotation);
            // 获得从参数的名称
            String parameterName = parameterNames[i];
            // 获取参数的类型
            Class<?> parameterType = parameterTypes[i ++];

            // 设置参数信息类
            ApiParamInfo apiParamInfo = new ApiParamInfo();
            apiParamInfo.setParamType(parameterType);

            // 如果参数注解的名字有值, 那我们就直接使用注解里面的参名字, 就使用参数名字了, 就像mvc里面的@param一样
            if (apiParam != null && StringUtils.isNotBlank(apiParam.value())){
                parameterName = apiParam.value();
            }

            // 把参数放到方法信息类中
            paramsMap.put(parameterName, apiParamInfo);
        }

        // 得到方法的返回值类型
        Class<?> returnType = method.getReturnType();
        // 我们让返回值必须是map或者void, 因为处理其他的类型很麻烦
        if (returnType != Void.class && !Map.class.isAssignableFrom(returnType)){
            throw new RuntimeException("接口的返回格式不对");
        }

        // 设置返回类型
        setReturnType(returnType);
    }

    private ApiParam getApiParamAnnotation(Annotation[] annotation) {
        // 如果是方法形参的参数就返回回来, 如果不是就返回一个null
        for (Annotation annotation1 : annotation) {
            if (annotation1 instanceof ApiParam){
                return (ApiParam)annotation1;
            }
        }
        return null;
    }

    public String getBeanName() {
        return beanName;
    }

    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public Class getReturnType() {
        return returnType;
    }

    public void setReturnType(Class returnType) {
        this.returnType = returnType;
    }
}
